package com.bms.dao.impl;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;

import javax.annotation.Resource;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.springframework.stereotype.Repository;
import org.springframework.util.Assert;

import com.bms.dao.BaseDao;
import com.bms.httpModel.Pager;
import com.bms.httpModel.Pager.OrderType;

@Repository
public class BaseDaoImpl<T, PK extends Serializable> implements BaseDao<T, PK> {

	private Class<T> entityClass;

	protected SessionFactory sessionFactory;

	@Resource
	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}

	protected Session getSession() {
		return sessionFactory.getCurrentSession();
	}

	@SuppressWarnings("unchecked")
	public BaseDaoImpl() {
		this.entityClass = null;
		Class c = getClass();
		Type type = c.getGenericSuperclass();
		if (type instanceof ParameterizedType) {
			Type[] parameterizedType = ((ParameterizedType) type)
					.getActualTypeArguments();
			this.entityClass = (Class<T>) parameterizedType[0];
		}
	}

	public void delete(T entity) {
		Assert.notNull(entity, "entity is required");
		getSession().delete(entity);
	}

	private Query createQuery(String hql, Object... values) {
		Assert.notNull(hql, "hql string must not be null");
		Query q = getSession().createQuery(hql);
		if (values != null && values.length > 0) {
			for (int i = 0; i < values.length; i++) {
				q.setParameter(i, values[i]);
			}
		}
		return q;
	}

	@SuppressWarnings("unchecked")
	public List<T> find(String hql, Object... params) {
		Query q = this.createQuery(hql, params);
		return q.list();
	}

	@SuppressWarnings("unchecked")
	public Pager<T> findByPager(String hql, Pager<T> pager, Object... params) {
		Query q = this.createQuery(hql, params);
		List list = q.setFirstResult(
				(pager.getCurrPage() - 1) * pager.getPageSize()).setMaxResults(
				pager.getPageSize()).list();
		Long totalCount = getTotalCount(params);
		pager.setList(list);
		pager.setTotalCount(totalCount.intValue());
		return pager;
	}

	/**
	 * @param detachedCri
	 * @return
	 */
	private Criteria createCriteria(DetachedCriteria detachedCri) {
		if (detachedCri == null)
			detachedCri = DetachedCriteria.forClass(this.entityClass);
		return detachedCri.getExecutableCriteria(this.getSession());
	}

	@SuppressWarnings("unchecked")
	public Pager<T> findByPager(Pager<T> pager) {
		if (pager == null) {
			pager = new Pager<T>();
		}
		Integer currPage = pager.getCurrPage();
		Integer pageSize = pager.getPageSize();
		String orderBy = pager.getOrderBy();
		OrderType orderType = pager.getOrderType();

		Criteria criteria = this.createCriteria(pager.getDetachedCriteria());
		Long totalCount = (Long) (criteria
				.setProjection(Projections.rowCount()).uniqueResult());

		criteria.setProjection(null);
		criteria.setResultTransformer(CriteriaSpecification.ROOT_ENTITY);
		criteria.setFirstResult((currPage - 1) * pageSize);
		criteria.setMaxResults(pageSize);
		if (StringUtils.isNotEmpty(orderBy) && orderType != null) {
			if (orderType == OrderType.asc) {
				criteria.addOrder(Order.asc(orderBy));
			} else {
				criteria.addOrder(Order.desc(orderBy));
			}
		}
		pager.setTotalCount(totalCount.intValue());
		pager.setList(criteria.list());
		return pager;
	}

	@SuppressWarnings("unchecked")
	public T get(PK id) {
		Assert.notNull(id, "id is required");
		return (T) getSession().get(entityClass, id);
	}

	@SuppressWarnings("unchecked")
	public T get(String propertyName, Object value) {
		Assert.hasText(propertyName, "propertyName must not be empty");
		Assert.notNull(value, "value is required");
		String hql = "from " + entityClass.getName() + " where "
				+ propertyName + " = ?";
		Query q = this.createQuery(hql, value);
		return (T) q.uniqueResult();
	}

	@SuppressWarnings("unchecked")
	public List<T> getAll() {
		String hql = "from " + entityClass.getName();
		return this.createQuery(hql).list();
	}

	@SuppressWarnings("unchecked")
	public List<T> getLike(T entity, String... propertyNames) {
		Assert.notNull(entity, "entity is required");
		Assert.notEmpty(propertyNames, "propertyNames must not be empty");
		Criteria criteria = this.createCriteria(null);
		for (String property : propertyNames) {
			try {
				Object value = PropertyUtils.getProperty(entity, property);
				if (value instanceof String) {
					// MatchMode.ANYWHERE 相当于 %value%
					criteria.add(Restrictions.like(property, (String) value, MatchMode.ANYWHERE));
					criteria.addOrder(Order.asc(property));
				} else {
					criteria.add(Restrictions.eq(property, value));
					criteria.addOrder(Order.asc(property));
				}
			} catch (Exception e) {
				// 忽略无效的检索参考数据。
			}
		}
		return criteria.list();
	}

	@SuppressWarnings("unchecked")
	public List<T> getList(PK[] ids) {
		Assert.notEmpty(ids, "ids must not be empty");
		String hql = "from " + entityClass.getName() + " where id in(:ids)";
		return getSession().createQuery(hql).setParameterList("ids", ids).list();
	}

	@SuppressWarnings("unchecked")
	public List<T> getList(DetachedCriteria detachedCri) {
		return this.createCriteria(detachedCri).list();
	}

	@SuppressWarnings("unchecked")
	public List<T> getList(String propertyName, Object value) {
		Assert.hasText(propertyName, "propertyName must not be empty");
		Assert.notNull(value, "value is required");
		String hql = "from " + entityClass.getName() + " where " + propertyName + " = ?";
		return this.createQuery(hql, value).list();
	}

	@SuppressWarnings("unchecked")
	public List<T> getList(T entity, String... propertyNames) {
		Assert.notNull(entity, "entity is required");
		Assert.notEmpty(propertyNames, "propertyNames must not be empty");
		Criteria criteria = this.createCriteria(null);
		for (String property : propertyNames) {
			try {
				Object value = PropertyUtils.getProperty(entity, property);
				criteria.add(Restrictions.eq(property, value));
				criteria.addOrder(Order.asc(property));
			} catch (Exception ex) {
				// 忽略无效的检索参考数据。
			}
		}
		return criteria.list();
	}

	public Long getTotalCount(Object... paramsValues) {
		String hql = "select count(*) from " + entityClass.getName();
		Query q = this.createQuery(hql, paramsValues);
		return (Long) q.uniqueResult();
	}

	@SuppressWarnings("unchecked")
	public T load(PK id) {
		Assert.notNull(id, "id is required");
		return (T) getSession().load(entityClass, id);
	}

	@SuppressWarnings("unchecked")
	public PK save(T entity) {
		Assert.notNull(entity, "entity is required");
		return (PK) getSession().save(entity);
	}

	public void saveOrUpdate(T entity) {
		Assert.notNull(entity, "entity is required");
		getSession().saveOrUpdate(entity);
	}

	public void update(T entity) {
		Assert.notNull(entity, "entity is required");
		getSession().update(entity);
	}

}
